package com.cata.mall.security.util;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * @作者: Tao
 * @日期: 2025/10/11
 * @时间: 10:07
 * @描述: JwtToken生成的工具类<pre>
 *      JWT token的格式：header.payload.signature
 *      header的格式（算法、token的类型）：
 *      {"alg": "HS512","typ": "JWT"}
 *      payload的格式（用户名、创建时间、生成时间）：
 *      {"sub":"wang","created":1489079981393,"exp":1489684781}
 *      signature的生成算法：
 *      HMACSHA512(base64UrlEncode(header) + "." +base64UrlEncode(payload),secret)
 */
public class JwtTokenUtil {
    private static final Logger LOGGER = LoggerFactory.getLogger(JwtTokenUtil.class);
    private static final String CLAIM_KEY_USERNAME = "sub";
    private static final String CLAIM_KEY_CREATED = "created";
    @Value("${jwt.secret}")
    private String secret;
    @Value("${jwt.expiration}")
    private Long expiration;
    @Value("${jwt.tokenHead}")
    private String tokenHead;

    /**
     * 生成JWT令牌
     *
     * @param claims 包含在令牌中的声明信息
     * @return 生成的JWT令牌字符串
     */
    public String generateToken(Map<String, Object> claims) {
        // 使用JJWT库构建JWT令牌
        return Jwts.builder()
                .setClaims(claims)
                .setExpiration(generateExpirationDate())
                .signWith(SignatureAlgorithm.HS512, secret)
                .compact();
    }

    /**
     * 根据用户详情生成JWT令牌
     * @param userDetails 用户详情对象，包含用户名等信息
     * @return 生成的JWT令牌字符串
     */
    public String generateToken(UserDetails userDetails) {
        // 创建声明集合，用于存储令牌的基本信息
        Map<String, Object> claims = new HashMap<>();
        claims.put(CLAIM_KEY_USERNAME, userDetails.getUsername());
        claims.put(CLAIM_KEY_CREATED, new Date());
        return generateToken(claims);
    }


    /**
     * 从JWT token中解析出Claims对象
     *
     * @param token JWT令牌字符串
     * @return 解析成功的Claims对象，如果解析失败则返回null
     */
    private Claims getClaimsFromToken(String token) {
        Claims claims = null;
        try {
            // 使用JWT解析器验证token并提取载荷信息
            claims = Jwts.parser()
                    .setSigningKey(secret)
                    .parseClaimsJws(token)
                    .getBody();
        } catch (Exception e) {
            LOGGER.info("JWT格式验证失败:{}", token);
        }
        return claims;
    }

    /**
     * 生成过期日期
     *
     * @return 过期日期对象，基于当前时间加上预设的过期秒数计算得出
     */
    private Date generateExpirationDate() {
        // 计算过期时间：当前毫秒数 + 过期秒数转换为毫秒数
        return new Date(System.currentTimeMillis() + expiration * 1000);
    }

    /**
     * 从JWT token中提取用户名
     *
     * @param token JWT token字符串
     * @return 返回从token中解析出的用户名，如果解析失败则返回null
     */
    public String getUserNameFromToken(String token) {
        String username;
        try {
            // 解析token获取claims信息
            Claims claims = getClaimsFromToken(token);
            // 从claims中获取subject字段作为用户名
            username = claims.getSubject();
        } catch (Exception e) {
            // 解析过程中出现异常，返回null
            username = null;
        }
        return username;
    }

    /**
     * 验证JWT token的有效性
     *
     * @param token 待验证的token字符串
     * @param userDetails 包含用户详细信息的对象
     * @return 如果token有效且未过期则返回true，否则返回false
     */
    public boolean validateToken(String token, UserDetails userDetails) {
        // 从token中提取用户名
        String username = getUserNameFromToken(token);
        // 验证用户名匹配且token未过期
        return username.equals(userDetails.getUsername()) && !isTokenExpired(token);
    }


    /**
     * 判断令牌是否已过期
     *
     * @param token 令牌字符串
     * @return 如果令牌已过期返回true，否则返回false
     */
    private boolean isTokenExpired(String token) {
        // 获取令牌的过期时间
        Date expiredDate = getExpiredDateFromToken(token);
        // 比较过期时间是否早于当前时间
        return expiredDate.before(new Date());
    }


    /**
     * 从JWT令牌中获取过期日期
     *
     * @param token JWT令牌字符串
     * @return 令牌的过期日期
     */
    private Date getExpiredDateFromToken(String token) {
        // 解析令牌获取声明信息
        Claims claims = getClaimsFromToken(token);
        // 返回声明中的过期时间
        return claims.getExpiration();
    }

    /**
     * 刷新头部令牌
     * @param oldToken 旧的令牌字符串
     * @return 刷新后的令牌字符串，如果刷新失败则返回null
     */
    public String refreshHeadToken(String oldToken) {
        if(StrUtil.isEmpty(oldToken)){
            return null;
        }
        String token = oldToken.substring(tokenHead.length());
        if(StrUtil.isEmpty(token)){
            return null;
        }
        //token校验不通过
        Claims claims = getClaimsFromToken(token);
        if(claims==null){
            return null;
        }
        //如果token已经过期，不支持刷新
        if(isTokenExpired(token)){
            return null;
        }
        //如果token在30分钟之内刚刷新过，返回原token
        if(tokenRefreshJustBefore(token,30*60)){
            return token;
        }else{
            claims.put(CLAIM_KEY_CREATED, new Date());
            return generateToken(claims);
        }
    }


    /**
     * 判断token在指定时间内是否刚刚刷新过
     * @param token 原token
     * @param time 指定时间（秒）
     */
    private boolean tokenRefreshJustBefore(String token, int time) {
        Claims claims = getClaimsFromToken(token);
        Date created = claims.get(CLAIM_KEY_CREATED, Date.class);
        Date refreshDate = new Date();
        //刷新时间在创建时间的指定时间内
        return refreshDate.after(created) && refreshDate.before(DateUtil.offsetSecond(created, time));
    }
}

